import gnu.io.*;
import java.util.*;
import java.io.*;


/**
 * The input/output basic functionality. 
 * 
 * This holds a lot of the data, but surprisingly enough, doesn;t
 * actually fire anything, as the thread from pir_io_reader will 
 * be the one calling the shots, as they are. 
 * 
 * This mostly handles setting up the IO port, and dealing with out-
 * going comms to the serial port, and is the median point for all
 * the other communications. 
 * 
 * @author Sam Reese
 * @see pir
 * @see pir_io_reader
 * @see pir_sequencer
 */
public class pir_io {

	protected int effects;
	private SerialPort port;
	private InputStream is;  
	private PrintStream os;
	private pir_io_reader is_reader;
	private pir_sock pirsock;
	private pir_sequencer mySequences;
	
	public final static boolean DEBUG_SERIAL = true;
	public final static boolean DEBUG_NET = true;
	public final static boolean DEBUG = true;
	
	public final int[][] PIR_EFFECT_ADDRESSES = {
			 {0,0,0,0}, // 0 (null)
		     {32,33,34,35}, // 1 
		     {36,37,38,39}, // 2
		     {40,41,42,43}, // 3
		     {44,45,46,47}, // 4
		     {48,49,50,51}, // 5
		     {52,53,54,55}, // 6
		     {56,57,58,59}, // 7
		     {92,93,94,95}, // 8
		     {64,65,66,67}, // 9
		     {68,69,70,71}, // 10
		     {72,73,74,75}, // 11
		     {76,77,78,79}, // 12
		     {80,81,82,83}, // 13
		     {84,85,86,87}, // 14
		     {88,89,90,91}, // 15
		     {60,61,62,63}, // 16
	};
	
	/**
	 * list of all sensor addresses, with padded index 0 = 0 for 
	 * use as 1..16
	 */
	public final int[] PIR_SENSOR_ADDRESSES = 
			{0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

	public final int PIR_SENSOR_HIGH_START = 50;
	public final int PIR_SENSOR_HIGH_END = 255;
	public final int PIR_SENSOR_THRESH = 2;
	
	public final int PIR_HIGH_OFF = 0;
	public final int PIR_HIGH_ON = 1;
	public final int PIR_LOW_OFF = 2;
	public final int PIR_LOW_ON = 3;
	
	
	/**
	 * Constructor and Initializer of all serial comms.
	 * 
	 * Oh how I dread this function not working. 
	 * 
	 * @param number_effects number of effects 
	 * @param desiredPort 
	 */
	public pir_io(int number_effects, String desiredPort){
		effects = number_effects;
		
	
		
			//CODE from RXTX Serial HOWTO 
			//
			// Get an enumeration of all ports known to JavaComm
			//
			Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
			//
			// Check each port identifier if 
			//   (a) it indicates a serial (not a parallel) port, and
			//   (b) matches the desired name.
			//
			CommPortIdentifier portId = null;  // will be set if port found
			while (portIdentifiers.hasMoreElements())
			{
			    CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
			  System.out.println(" port name " + pid.getName());
			    if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
			       pid.getName().equals(desiredPort)) 
			    {
			        portId = pid;
			        break;
			    }
			}
			if(portId == null)
			{
			    System.err.println("Could not find serial port " + desiredPort);
			    System.exit(1);
			}
			// Use port identifier for acquiring the port
			//
			port = null;
			try {
			    port = (SerialPort) portId.open(
			        "2pir", // Name of the application asking for the port 
			        10000   // Wait max. 10 sec. to acquire port
			    );
			} catch(PortInUseException e) {
			    System.err.println("Port already in use: " + e);
			    System.exit(1);
			}
			//
			// Now we are granted exclusive access to the particular serial
			// port. We can configure it and obtain input and output streams.
			//

			try{
			port.setSerialPortParams(19200,SerialPort.DATABITS_8,SerialPort.STOPBITS_2,SerialPort.PARITY_ODD);
			port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
			}catch (UnsupportedCommOperationException e) {
				System.out.println("Error setting params" + e);
				System.exit(1);
			}
			is = null;  
			os = null;

			try {
			  is = port.getInputStream();
			} catch (IOException e) {
			  System.err.println("Can't open input stream: write-only");
				if (port != null) port.close();
			  System.exit(1);
			}
			try{
			os = new PrintStream(port.getOutputStream(), true);
			}catch (IOException e){
				  System.err.println("Can't open input stream: write-only");
					try{
						if (is != null) { is.close(); System.out.println(" Input stream aborted!"); }
						if (port != null) port.close();
					}catch(IOException e2){
							System.out.println("Error on close");
					}
				  System.exit(1);
			}

			//create reader thread and fire it off.
			//from this moment on, it is reading and running.
			//by doing this, I am using blocking IO, but
			//throwing it on a thread to stop fuckup stalls.
			is_reader = new pir_io_reader(this, is);
			is_reader.start();
			System.out.println("Port successfully open! resetting all effects");
			
			reset();
		/*	for(int i=1; i < effects; i++){
				fire(i, PIR_HIGH_OFF);
				fire(i, PIR_LOW_OFF);
			}*/
			
			System.out.println("loading sequences...");
			mySequences = new pir_sequencer("pir.seq", this);
			System.out.println("Listening on " + mySequences.count()+ " sequences");
			pirsock = new pir_sock(this);
			pirsock.start();
			System.out.println("pir_io initialized");
			System.out.println("Port " + desiredPort + " Ok.");
			System.out.println("Listening on port " + pirsock.getPort() + " for internet command");
			System.out.println("All Systems Go?!+");
	}
	
	public void instSequence(int effect, String instantsequence){
		mySequences.createAndTrigger(effect, instantsequence);
	}
	
	
	/**
	 * defualt constructor PLATFORM DEPENDANT. 
	 * 
	 * defualt: 16 effects. duh. 
	 * 
	 * 
	 * COMM1 for windows
	 * linux:  /dev/ttyS1
	 */
	public pir_io(){
		this(16,"COM1");
		// this(16,"/dev/ttyS1);
	}
	
	/**
	 * recieves input from the IO reader. Totally misnamed, as 
	 * it is called and actuated from the pir_io_reader thread.
	 * 
	 * @param inst 
	 * @see pir_io_reader
	 */
	public boolean getInput(int inst){
		
		return false;
	}
	
	/**
	 * Intended to send something to the output bit of the data port.
	 * probably unused. 
	 * 
	 * @param text text to send
	 * @return status of sending
	 */
	public boolean sendToPort(int c){
		os.write(c);
		return true;
	}
	
	
	/**
	 * Changes the status of a single solenoid. 
	 * 
	 * Really should be named something different, but is left 
	 * as a legacy of Ian's fire_the_fucking_flamethrowers_oh_my_god
	 * procedure in the original 2pir perl
	 * 
	 * potential status values are:
	 * PIR_HIGH_OFF 0
	 * PIR_HIGH_ON 1
	 * PIR_LOW_OFF 2
	 * PIR_LOW_ON 3
	 * 
	 * @param effect the number of the effect to change status on
	 * @param status one of the four potential values
	 */
	public boolean fire(int effect, int status){
		if (effect > effects){
			return false;
		}
		if (pirsock != null){
			pirsock.sendStatus(effect, status);
		}
		
		    System.out.println("Change relay : " + effect + " to " + status );
			os.write(PIR_EFFECT_ADDRESSES[effect][status]);
		    os.flush();
		if(status == PIR_HIGH_ON){
			mySequences.pass(effect);
		}
			return true;
	}
	
	/**
	 * More appropriately named version of fire.
	 * 
	 * @see fire(int, int)
	 * @param effect
	 * @param status
	 * @return
	 */
	public boolean setRelay(int effect, int status){
		return fire(effect, status);
	}
	
	/**
	 * Creates a sequence to fire an effect for n times. 
	 * 
	 * currently does not work. i
	 * 
	 * @param effect
	 * @param time
	 * @return
	 */
	public boolean pulse(int effect, int time){
		
	 return false;	
	}
	
	/**
	 * Uses fire to turn off all effects. 
	 * 
	 * @return status. 
	 */
	public boolean reset (){
		for (int i = 1; i <= effects; i++){
			setRelay(i, PIR_HIGH_OFF);
			setRelay(i, PIR_LOW_OFF);
		}
		return true;
	}
	
	/**
	 * Trys to close out the ports and sockets. 
	 * 
	 * @return status: not necessary. 
	 */
	public boolean close(){
		try{
			if (is != null) is.close();
		
		if (os != null) os.close();
		if (port != null) port.close();
		}catch(IOException e){
			System.out.println("Error on close" + e);
		}
		pirsock.close();
		
		return true;
	}
	//sit and spin
	void run(){
		try{
			while(System.in.read() != 'Q'){
				try{
				Thread.sleep(1000);
				int c = is.read();
				System.out.println("IS = " + c);
				}catch (InterruptedException ie){
					System.out.println("Interupt to main thread! Ignore!!!");
				}
			}
			close();
		}catch (IOException e){
			System.out.println("Exception on main thread!" + e);
		}
	}
	
}
